
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class AnalizadorSintactico
{

	public static final Integer S = 0,
			SP = 1,
			FUNCION = 2,
			TIPO = 3,
			BLOQUE = 4,
			SECINSTR = 5,
			V = 6,
			INSTR = 7,
			EXPR = 8,
			EXPR2 = 9,
			TERM = 10,
			TERM2 = 11,
			FACTOR = 12;
	private Token preanalisis;
	private AnalizadorLexico lexer;
	private HashMap<Integer, ArrayList<ArrayList<Integer>>> predicciones;
	private TablaSimbolos tablaSimbolos;
	private Operaciones operaciones;
	
	private Integer nivel;
	private Integer factorAnterior;
	private String funcionActual;

	public AnalizadorSintactico(String path)
	{
		lexer = new AnalizadorLexico(path);
		ArrayList<ArrayList<Integer>> conjunto = new ArrayList<ArrayList<Integer>>();
		ArrayList<Integer> pred = new ArrayList<Integer>();
		predicciones = new HashMap<Integer, ArrayList<ArrayList<Integer>>>();
		tablaSimbolos = new TablaSimbolos();
		nivel=0;
		funcionActual="";
		factorAnterior = null;
		operaciones = new Operaciones();

		//Creamos los conjuntos de predicción para cada una de las reglas que se van a poder ejecutar en el analizador.

		pred.add(Token.INT);
		pred.add(Token.DOUBLE);
		pred.add(Token.MAIN);
		conjunto.add(pred);
		predicciones.put(S, conjunto);

		conjunto = new ArrayList<ArrayList<Integer>>();
		pred = new ArrayList<Integer>();
		pred.add(Token.INT);
		pred.add(Token.DOUBLE);
		conjunto.add(pred);
		pred = new ArrayList<Integer>();
		pred.add(Token.MAIN);
		conjunto.add(pred);
		predicciones.put(SP, conjunto);

		conjunto = new ArrayList<ArrayList<Integer>>();
		pred = new ArrayList<Integer>();
		pred.add(Token.INT);
		pred.add(Token.DOUBLE);
		conjunto.add(pred);
		predicciones.put(FUNCION, conjunto);

		conjunto = new ArrayList<ArrayList<Integer>>();
		pred = new ArrayList<Integer>();
		pred.add(Token.INT);
		conjunto.add(pred);
		pred = new ArrayList<Integer>();
		pred.add(Token.DOUBLE);
		conjunto.add(pred);
		predicciones.put(TIPO, conjunto);

		conjunto = new ArrayList<ArrayList<Integer>>();
		pred = new ArrayList<Integer>();
		pred.add(Token.LLAVEI);
		conjunto.add(pred);
		predicciones.put(BLOQUE, conjunto);

		conjunto = new ArrayList<ArrayList<Integer>>();
		pred = new ArrayList<Integer>();
		pred.add(Token.INT);
		pred.add(Token.DOUBLE);
		conjunto.add(pred);
		pred = new ArrayList<Integer>();
		pred.add(Token.ID);
		pred.add(Token.LLAVEI);
		conjunto.add(pred);
		pred = new ArrayList<Integer>();
		pred.add(Token.LLAVED);
		conjunto.add(pred);
		predicciones.put(SECINSTR, conjunto);

		conjunto = new ArrayList<ArrayList<Integer>>();
		pred = new ArrayList<Integer>();
		pred.add(Token.INT);
		pred.add(Token.DOUBLE);
		conjunto.add(pred);
		predicciones.put(V, conjunto);

		conjunto = new ArrayList<ArrayList<Integer>>();
		pred = new ArrayList<Integer>();
		pred.add(Token.ID);
		conjunto.add(pred);
		pred = new ArrayList<Integer>();
		pred.add(Token.LLAVEI);
		conjunto.add(pred);
		predicciones.put(INSTR, conjunto);

		conjunto = new ArrayList<ArrayList<Integer>>();
		pred = new ArrayList<Integer>();
		pred.add(Token.REAL);
		pred.add(Token.ENTERO);
		pred.add(Token.ID);
		conjunto.add(pred);
		predicciones.put(EXPR, conjunto);

		conjunto = new ArrayList<ArrayList<Integer>>();
		pred = new ArrayList<Integer>();
		pred.add(Token.OPSUM);
		conjunto.add(pred);
		pred = new ArrayList<Integer>();
		pred.add(Token.PYC);
		conjunto.add(pred);
		predicciones.put(EXPR2, conjunto);

		conjunto = new ArrayList<ArrayList<Integer>>();
		pred = new ArrayList<Integer>();
		pred.add(Token.REAL);
		pred.add(Token.ENTERO);
		pred.add(Token.ID);
		conjunto.add(pred);
		predicciones.put(TERM, conjunto);

		conjunto = new ArrayList<ArrayList<Integer>>();
		pred = new ArrayList<Integer>();
		pred.add(Token.OPMUL);
		conjunto.add(pred);
		pred = new ArrayList<Integer>();
		pred.add(Token.OPSUM);
		pred.add(Token.PYC);
		conjunto.add(pred);
		predicciones.put(TERM2, conjunto);

		conjunto = new ArrayList<ArrayList<Integer>>();
		pred = new ArrayList<Integer>();
		pred.add(Token.REAL);
		conjunto.add(pred);
		pred = new ArrayList<Integer>();
		pred.add(Token.ENTERO);
		conjunto.add(pred);
		pred = new ArrayList<Integer>();
		pred.add(Token.ID);
		conjunto.add(pred);
		predicciones.put(FACTOR, conjunto);

	}

	public String S() throws Excepcion
	{
		StringBuilder builder = new StringBuilder();
		builder.append("class TradC {\n");
		if (predicciones.get(S).get(0).contains(preanalisis.getTipo()))
		{
			builder.append(Sp());
			emparejar(Token.MAIN);
			tablaSimbolos.addSimbolo( new Simbolo(nivel, Simbolo.FUNCION, "main", null) );
			emparejar(Token.PARI);
			emparejar(Token.PARD);
			builder.append("\npublic static void main(String[] args) {\n");
			funcionActual="main";
			builder.append(Bloque());
			builder.append("\n").append(operaciones.toString());
			builder.append("}\n}");
		} else
		{
                        ArrayList<String> lista=new ArrayList<String>();
                        lista.add("'double'");
                        lista.add("'int'");
                        lista.add("'main'");
			LanzarError(lista);
		}
		emparejar(Token.EOF); //Revisar!!!!!!!!!!!!!!!!!!!!!!!!!!!!
		return builder.toString();
	}

	private String Sp() throws Excepcion
	{
		StringBuilder builder = new StringBuilder();

		if (predicciones.get(SP).get(0).contains(preanalisis.getTipo()))
		{
			builder.append(Funcion());
			builder.append(Sp());
		} else if (!predicciones.get(SP).get(1).contains(preanalisis.getTipo())) //Cadena vacía
		{
			ArrayList<String> lista=new ArrayList<String>();
                        lista.add("'double'");
                        lista.add("'int'");
                        lista.add("'main'");
			LanzarError(lista);
		}

		return builder.toString();
	}

	private String Funcion() throws Excepcion
	{
		StringBuilder builder = new StringBuilder();

		if (predicciones.get(FUNCION).get(0).contains(preanalisis.getTipo()))
		{
			builder.append("\npublic static ");
			builder.append(Tipo(true));
			funcionActual = emparejar(Token.ID).getLexema();
			tablaSimbolos.addSimbolo( new Simbolo(nivel, Simbolo.FUNCION, funcionActual, null) );
			builder.append(funcionActual);
			builder.append(emparejar(Token.PARI).getLexema());
			builder.append(emparejar(Token.PARD).getLexema());
			builder.append(" {\n");
			builder.append(Bloque());
			builder.append("\n").append(operaciones.toString());
			builder.append("}\n");
			
		} else
		{
			ArrayList<String> lista=new ArrayList<String>();
                        lista.add("'double'");
                        lista.add("'int'");
			LanzarError(lista);
		}

		return builder.toString();
	}

	private String Tipo(boolean isFuncion) throws Excepcion
	{
		StringBuilder builder = new StringBuilder();

		if (predicciones.get(TIPO).get(0).contains(preanalisis.getTipo()))
		{
			emparejar(Token.INT);
			if (!isFuncion)
			{
				builder.append("  Integer");
			} else
			{
				builder.append("integer ");
			}
		} 
		else if (predicciones.get(TIPO).get(1).contains(preanalisis.getTipo()))
		{
			emparejar(Token.DOUBLE);
			if (!isFuncion)
			{
				builder.append("  Double");
			} else
			{
				builder.append("double ");
			}
		} 
		else
		{
			ArrayList<String> lista=new ArrayList<String>();
                        lista.add("'double'");
                        lista.add("'int'");
			LanzarError(lista);
		}

		return builder.toString();
	}

	private String Bloque() throws Excepcion
	{
		StringBuilder builder = new StringBuilder();

		if (predicciones.get(BLOQUE).get(0).contains(preanalisis.getTipo()))
		{
			emparejar(Token.LLAVEI);
			tablaSimbolos = tablaSimbolos.push();
			nivel++;
			builder.append(SecInstr());
			emparejar(Token.LLAVED);
			tablaSimbolos = tablaSimbolos.pop();
			nivel--;
		} else
		{
			ArrayList<String> lista=new ArrayList<String>();
                        lista.add("'{'");
			LanzarError(lista);
		}

		return builder.toString();
	}

	private String SecInstr() throws Excepcion
	{
		StringBuilder builder = new StringBuilder();

		if (predicciones.get(SECINSTR).get(0).contains(preanalisis.getTipo()))
		{
			builder.append(V());
			builder.append(SecInstr());
		} else if (predicciones.get(SECINSTR).get(1).contains(preanalisis.getTipo()))
		{
			builder.append(Instr());
			builder.append(SecInstr());
		} else if (!predicciones.get(SECINSTR).get(2).contains(preanalisis.getTipo())) //Cadena vacia
		{
			ArrayList<String> lista=new ArrayList<String>();
                        lista.add("'double'");
                        lista.add("'int'");
                        lista.add("'{'");
                        lista.add("'}'");
                        lista.add("identificador");
			LanzarError(lista);
		}

		return builder.toString();
	}

	private String V() throws Excepcion
	{
		StringBuilder builder = new StringBuilder();
		String tipo;
		Token idVar;

		if (predicciones.get(V).get(0).contains(preanalisis.getTipo()))
		{
			//CAMBIAAAAAAAAAAAAAAR NIVEL (AHORA ES 0)!!!!
			tipo = Tipo(false);
			builder.append(tipo).append(" ");
			idVar = emparejar(Token.ID);
			emparejar(Token.PYC);
			Simbolo s;
			if ( tipo.equals("  Double") )
			{
				s = new Simbolo(nivel, Simbolo.REAL, idVar.getLexema(), funcionActual);
			}
			else
			{
				s = new Simbolo(nivel, Simbolo.ENTERO, idVar.getLexema(), funcionActual);
			}
			builder.append(s.toString()).append(";\n");
			if ( !tablaSimbolos.addSimbolo(s) )
			{
				LanzarError5( idVar );
			}
		} 
		else
		{
			ArrayList<String> lista=new ArrayList<String>();
                        lista.add("'double'");
                        lista.add("'int'");
			LanzarError(lista);
		}

		return builder.toString();
	}

	private String Instr() throws Excepcion
	{
		StringBuilder builder = new StringBuilder();
		Token t;

		if (predicciones.get(INSTR).get(0).contains(preanalisis.getTipo()))
		{
			t = emparejar(Token.ID);
			Simbolo s = new Simbolo(nivel, 0, t.getLexema(), funcionActual);
			if ( !tablaSimbolos.existe(s) )
			{
				LanzarError6(t);
			}
			else
			{
				s = tablaSimbolos.getSimbolo(t.getLexema());
			}
			operaciones.addOperacion();
			operaciones.appendOperando(s);
			emparejar(Token.ASIG);
			operaciones.appendOperador("=");
			Expr(s,t);
			emparejar(Token.PYC);
			operaciones.appendOperador(";");
		} else if (predicciones.get(INSTR).get(1).contains(preanalisis.getTipo()))
		{
			builder.append(Bloque());
		} else
		{
			ArrayList<String> lista=new ArrayList<String>();
                        lista.add("'{'");
                        lista.add("identificador");
			LanzarError(lista);
		}

		return builder.toString();
	}

	private String Expr( Simbolo s, Token t ) throws Excepcion
	{
		StringBuilder builder = new StringBuilder();

		if (predicciones.get(EXPR).get(0).contains(preanalisis.getTipo()))
		{
			Term(s,t);
			Expr2(s,t);
		} 
		else
		{
			ArrayList<String> lista=new ArrayList<String>();
                        lista.add("entero");
                        lista.add("identificador");
                        lista.add("real");
			LanzarError(lista);
		}

		return builder.toString();
	}

	private String Expr2( Simbolo s, Token t ) throws Excepcion
	{
		StringBuilder builder = new StringBuilder();
		Token opsum;

		if (predicciones.get(EXPR2).get(0).contains(preanalisis.getTipo()))
		{
			opsum = emparejar(Token.OPSUM);
			operaciones.appendOperador(opsum.getLexema());
			Term(s,t);
			Expr2(s,t);
		} else if (!predicciones.get(EXPR2).get(1).contains(preanalisis.getTipo())) //Cadena vacia
		{
			ArrayList<String> lista=new ArrayList<String>();
                        lista.add("'+' o '-'");
                        lista.add("';'");
			LanzarError(lista);
		}

		return builder.toString();
	}

	private String Term( Simbolo s, Token t ) throws Excepcion
	{
		StringBuilder builder = new StringBuilder();

		if (predicciones.get(TERM).get(0).contains(preanalisis.getTipo()))
		{
			Factor(s,t);
			Term2(s,t);
		} else
		{
			ArrayList<String> lista=new ArrayList<String>();
                        lista.add("entero");
                        lista.add("identificador");
                        lista.add("real");
			LanzarError(lista);
		}

		return builder.toString();
	}

	private String Term2( Simbolo s, Token t ) throws Excepcion
	{
		StringBuilder builder = new StringBuilder();
		Token opmul;

		if (predicciones.get(TERM2).get(0).contains(preanalisis.getTipo()))
		{
			opmul = emparejar(Token.OPMUL);
			operaciones.appendOperador(opmul.getLexema());
			Factor(s,t);
			Term2(s,t);
		} else if (!predicciones.get(TERM2).get(1).contains(preanalisis.getTipo()))
		{
			ArrayList<String> lista=new ArrayList<String>();
                        lista.add("'*' o '/'");
                        lista.add("'+' o '-'");
                        lista.add("';'");
			LanzarError(lista);
		}

		return builder.toString();
	}

	private String Factor( Simbolo s, Token t ) throws Excepcion
	{
		StringBuilder builder = new StringBuilder();
		Token factor;

		if (predicciones.get(FACTOR).get(0).contains(preanalisis.getTipo()))
		{
			factor = emparejar(Token.REAL);
			if ( s.getTipo() == Simbolo.ENTERO )
			{
				LanzarError8(t);
			}
			operaciones.appendOperando( new Simbolo(0, Simbolo.DOUBLE, factor.getLexema(), "") );
		} 
		else if (predicciones.get(FACTOR).get(1).contains(preanalisis.getTipo()))
		{
			factor = emparejar(Token.ENTERO);
			operaciones.appendOperando( new Simbolo(0, Simbolo.INT, factor.getLexema(), "") );
		}
		else if (predicciones.get(FACTOR).get(2).contains(preanalisis.getTipo()))
		{
			Token varId = emparejar(Token.ID);
			Simbolo sId = new Simbolo(nivel, 0, varId.getLexema(), funcionActual) ;
			if ( !tablaSimbolos.existe(sId) )
			{
				LanzarError6( varId );
			}
			sId = tablaSimbolos.getSimbolo(sId.getNombre());
			if ( tablaSimbolos.isFuncion(sId) )
			{
				LanzarError7(varId);
			}
			if ( sId.getTipo() == Simbolo.REAL && s.getTipo() == Simbolo.ENTERO )
			{
				LanzarError8(t);
			}
			operaciones.appendOperando( sId );
		}
		else
		{
			ArrayList<String> lista=new ArrayList<String>();
                        lista.add("entero");
                        lista.add("identificador");
                        lista.add("real");
			LanzarError(lista);
		}

		return builder.toString();
	}

	private Token emparejar(int tokenEsperado) throws Excepcion
	{
		Token token = null;
		if (preanalisis.getTipo() == tokenEsperado)
		{
			token = preanalisis;
			preanalisis = lexer.NextToken();
		} else
		{
			StringBuilder error = new StringBuilder();
			error.append("Error 3 (");
			error.append(preanalisis.getFila());
			error.append(",");
			error.append(preanalisis.getColumna());
			error.append("): encontrado '");
			error.append(preanalisis.getLexema());
			error.append("' esperaba ");
			error.append(Token.SalidaError[ tokenEsperado]);
			throw new Excepcion(error.toString());
		}

		return token;

	}

	public void Run() throws Excepcion
	{
		preanalisis = lexer.NextToken();
		System.out.println(S());
	}

	public void LanzarError5( Token t ) throws Excepcion
	{
		StringBuilder bError = new StringBuilder();
		bError.append("Error 5 (");
		bError.append(t.getFila());
		bError.append(",");
		bError.append(t.getColumna());
		bError.append("): '");
		bError.append(t.getLexema());
		bError.append("' ya existe en este ambito");

		throw new Excepcion(bError.toString());
	}
	
	public void LanzarError6( Token t ) throws Excepcion
	{
		StringBuilder bError = new StringBuilder();
		bError.append("Error 6 (");
		bError.append(t.getFila());
		bError.append(",");
		bError.append(t.getColumna());
		bError.append("): '");
		bError.append(t.getLexema());
		bError.append("' no ha sido declarado");

		throw new Excepcion(bError.toString());
	}
	
	public void LanzarError7( Token t ) throws Excepcion
	{
		StringBuilder bError = new StringBuilder();
		bError.append("Error 7 (");
		bError.append(t.getFila());
		bError.append(",");
		bError.append(t.getColumna());
		bError.append("): '");
		bError.append(t.getLexema());
		bError.append("' no es una variable");

		throw new Excepcion(bError.toString());
	}
	
	public void LanzarError8( Token t ) throws Excepcion
	{
		StringBuilder bError = new StringBuilder();
		bError.append("Error 8 (");
		bError.append(t.getFila());
		bError.append(",");
		bError.append(t.getColumna());
		bError.append("): '");
		bError.append(t.getLexema());
		bError.append("' debe ser de tipo real");

		throw new Excepcion(bError.toString());
	}
	
	public void LanzarError(ArrayList<String> lista) throws Excepcion
	{
		StringBuilder error = new StringBuilder();
		if (preanalisis.getTipo() != Token.EOF)
		{
			error.append("Error 3 (");
			error.append(preanalisis.getFila());
			error.append(",");
			error.append(preanalisis.getColumna());
			error.append("): encontrado '");
			error.append(preanalisis.getLexema());
			error.append("', esperaba ");
			for(int i=0;i<lista.size();i++){
                            error.append(lista.get(i));
                            if(i<lista.size()-1){
                                error.append(" ");
                            }
                        }
		} else
		{
			error.append("Error 4: encontrado final de fichero, esperaba ");
			for(int i=0;i<lista.size();i++){
                            error.append(lista.get(i));
                            if(i<lista.size()-1){
                                error.append(" ");
                            }
                        }
		}
		throw new Excepcion(error.toString());
	}

	private String getLista(Integer id)
	{
		StringBuilder builder = new StringBuilder();
		for (int i = 0; i < predicciones.get(id).size(); i++)
		{
			for (int j = 0; j < predicciones.get(id).get(i).size(); j++)
			{
				builder.append(Token.SalidaError[ predicciones.get(id).get(i).get(j)]);
				if (i != (predicciones.get(id).size() - 1) || j != (predicciones.get(id).get(i).size() - 1))
				{
					builder.append(" ");
				}
			}
		}
		return builder.toString();
	}
}
